home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Visual Cafe 3
/
Visual Cafe 3.ISO
/
Vcafe
/
JFC.bin
/
DateChooser.java
< prev
next >
Wrap
Text File
|
1998-06-30
|
67KB
|
1,995 lines
/*
* @(#)DateChooser.java 1.22 11/26/97
*
* Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
* SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
* SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*
* @author James Gosling
* @author Brian Gerhold
* rev 1.0
* 08/14/97
*/
package com.sun.java.swing;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.text.*;
import com.sun.java.swing.border.*;
/* The DateChooser is a Component used for fast and easy
* date and/or time selection. It is highly configurable and
* cutomizable.
*/
public class DateChooser extends Container implements ActionListener,
AdjustmentListener {
/** The most compact style. Months will be numbered, not named. */
public static final int TINY = 0;
/** A fairly compact style. Months will be named by their abbreviations. */
public static final int SMALL = 1;
/** A more expansive style. Month names will not be abbreviated. */
public static final int MEDIUM = 2;
/** A verbose style that displays a full calendar and/or clock face */
public static final int LARGE = 3;
/** Display only the date */
public static final int DATE_MODE = 0;
/** Display only the time */
public static final int TIME_MODE = 1;
/** Display both the date and time */
public static final int DATE_TIME_MODE = 2;
/*protected*/private String[] months; //String array of month names or numbers
/*protected*/private String[] bigmonths; //String array of fullblown month names
/*protected*/private Calendar cal; //Calendar class to store date/time information
/*protected*/private Locale myLocale; //optional Locale for i18n purposes
/*protected*/private TimeZone myZone; //optional TimeZone for i18n purposes
/*protected*/private JButton myCalendarButton; //button for pop-up calendar
/*protected*/private JButton myClockButton; //button for pop-up clock
/*protected*/private JPopupMenu ClockPop; //pop up to hold ClockFace
/*protected*/private Spinner month, year,day, hours, minutes, ampm; //data fields
/*protected*/private MiniCal minical; //calendar that shows in pop-up
/*protected*/private ClockFace clockface; //clockface that shows in pop-up
/*protected*/private int myMode; //mode
/*protected*/private int myStyle; //style
/*protected*/private JPanel panel1; //holds Spinner fields and buttons for pop-ups
/*protected*/private JPanel panel2;//used only in LARGE style, used to hold cal and clock
/*protected*/private JPanel datePane; //encompasses date fields and "CAL" button
/*protected*/private JPanel timePane; //encompasses time fields and "CLOCK" button
/*protected*/private int daysInWeek; //so that the Calendar stuff is not hard-wired
/*protected*/private DateFormatSymbols dfd; //used to generate Locale specific info
/*protected*/private char[] format; //stores format of date and time fields
/*protected*/private boolean TwelveHourClock; //Locale specific
/*protected*/private int dateOrder; //derived from parsing format
/*protected*/private int timeOrder; //derived from parsing form
/*protected*/private char[] monthyear; //String separator between month and year
/*protected*/private char[] monthdate; //String separator between month and date
/*protected*/private char[] dateyear; //String separator between date and year
/*protected*/private char[] hourminute; //String separator between hour and minute
/* generally the DateChooser will take a mode and a style in its construcor
* however Locale and TimeZone are added as optional arguments for i18n
*/
public DateChooser(int mode, int style){
init(mode, style, TimeZone.getDefault(), Locale.getDefault());
}
public DateChooser(int mode, int style, TimeZone zone){
init(mode, style, zone, Locale.getDefault());
}
public DateChooser(int mode, int style, Locale aLocale){
init(mode, style, TimeZone.getDefault(), aLocale);
}
public DateChooser(int mode, int style, TimeZone zone, Locale aLocale) {
init(mode, style, zone, aLocale);
}
//with optional Locale and TimeZone info set, internal constructor is called
private void init(int mode, int style, TimeZone zone, Locale aLocale) {
//trap out of bounds arguments
if((mode < 0) || (mode > 2)){
throw new IllegalArgumentException
("Mode for DateChooser must be 0, 1, or 2");
}
if((style < 0) || (style > 3)){
throw new IllegalArgumentException
("Style for DateChooser must be 0, 1,2, or 3");
}
//initialize Calendar and set DateChooser layout
cal = Calendar.getInstance(zone, aLocale);
setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
//instantiate panel1, setLayout
panel1 = new JPanel(false);
panel1.setLayout(new FlowLayout(FlowLayout.CENTER,0,0));
//store mode, style, timezone and Locale info
myMode = mode;
myStyle = style;
myZone = zone;
myLocale = aLocale;
/* retrieve the format style to be used by DateFormat in determining how to
* order fields and what size strings and digits to use
*/
int formatter = 0;
switch(myStyle){
case TINY:
formatter = DateFormat.SHORT;
break;
case SMALL:
formatter = DateFormat.LONG;
break;
case MEDIUM:
formatter = DateFormat.LONG;
break;
case LARGE:
formatter = DateFormat.LONG;
break;
}
//get a DateFormat
SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateTimeInstance
(formatter,formatter, myLocale);
/* this checks to see if the pattern string of the date and time is missing
* any of the fields. if so, the pattern string is defaulted to LONG
*/
String checkString = df.toPattern();
if((checkString.indexOf('M') == -1) || (checkString.indexOf('y') == -1) ||
(checkString.indexOf('d') == -1) || ((checkString.indexOf('h') == -1) &&
(checkString.indexOf('H') == -1))|| (checkString.indexOf('m') == -1)){
df = (SimpleDateFormat) DateFormat.getDateTimeInstance
(DateFormat.LONG,DateFormat.LONG, myLocale);
}
//set class wide variable dfd,use it to get Locale and format specific info
dfd = df.getDateFormatSymbols();
daysInWeek = (dfd.getShortWeekdays()).length - 1;
//set class wide variable format, used to order fields
//format = new char[((df.toPattern()).toCharArray()).length];
format = (df.toPattern()).toCharArray();
int i; //declare a temporary int for for loops and such
//instantiate panel2, set layout
panel2 = new JPanel(false);
panel2.setLayout(new FlowLayout(FlowLayout.CENTER,0,0));
//mode 0 or 2 indicates that date fields will be present
if ((myMode == DATE_MODE) || (myMode == DATE_TIME_MODE)){
/* set the JPanel that will hold the date fields using a
* protected function that may be overwritten in the event that the
* developer desires something more than what id the default. by default
* a FilledBorderedPane is returned (see class below
*/
datePane = createJPanel();
/* dateOrder is an integer derived from parsing the format char[]
* below is the list of values and corresponding field orderings:
*
* 0: month, day, year
* 1: month, year, day
* 2: day, month, year
* 3: day, year, month
* 4: year, month, day
* 5: year, day, month
*/
dateOrder = OrderParser('M','M','d','y');
/* instaniate a pop-up Calendar. the constructor takes and int because
* in the LARGE style, the minical will be permanently added to panel2,
* thus the calendar within minical will be set in a panel. otherwise
* the calendar within minical is set in a JPopUpMenu
*/
if(myStyle != LARGE){
minical = new MiniCal(0);
}
else{
minical = new MiniCal(1);
}
/* the SpinnerLinker is a quickly hacked together class that watches all
* three spinners a set, and will cycle between them on a key event
*/
SpinnerLinker linker1 = new SpinnerLinker(null,null,null);
/* given the dateOrder, add month, day, and year to the datePane in the
* correct order with the correct StringSeparators between them. also
* the order of the Spinners for the SpinnerLinker
*/
switch(dateOrder){
case 0:
monthdate = SeparatorParser('M','M','d');
addMonth(String.copyValueOf(monthdate));
dateyear = SeparatorParser('d','d','y');
addDate(String.copyValueOf(dateyear));
addYear(null);
linker1 = new SpinnerLinker(month,day,year);
break;
case 1:
monthyear = SeparatorParser('M','M','y');
addMonth(String.copyValueOf(monthyear));
dateyear = SeparatorParser('d','d','y');
addYear(String.copyValueOf(dateyear));
addDate(null);
linker1 = new SpinnerLinker(month,year,day);
break;
case 2:
monthdate = SeparatorParser('M','M','d');
addDate(String.copyValueOf(monthdate));
monthyear = SeparatorParser('M','M','y');
addMonth(String.copyValueOf(monthyear));
addYear(null);
linker1 = new SpinnerLinker(day,month,year);
break;
case 3:
dateyear = SeparatorParser('d','d','y');
addDate(String.copyValueOf(dateyear));
monthyear = SeparatorParser('M','M','y');
addYear(String.copyValueOf(monthyear));
addMonth(null);
linker1 = new SpinnerLinker(day,year,month);
break;
case 4:
monthyear = SeparatorParser('M','M','y');
addYear(String.copyValueOf(monthyear));
monthdate = SeparatorParser('M','M','d');
addMonth(String.copyValueOf(monthdate));
addDate(null);
linker1 = new SpinnerLinker(year,month,day);
break;
case 5:
dateyear = SeparatorParser('d','d','y');
addYear(String.copyValueOf(dateyear));
monthdate = SeparatorParser('M','M','d');
addDate(String.copyValueOf(monthdate));
addMonth(null);
linker1 = new SpinnerLinker(year,day,month);
break;
}
//set the SpinnerLinker to listen to all three spinners
month.addKeyListener(linker1);
day.addKeyListener(linker1);
year.addKeyListener(linker1);
/*if the style is greater than tiny, add the minical (see function below
*for details)
*/
if (myStyle > TINY){
addMiniCal();
}
/* the line "setDatePanePreferences()" calls a protected function that
* sets the prefernces for the datePane, primarily just the border
* setting. however, because the default from createJPanel is a
* FilledBorderedPane, it is neccessary to set the fill color outside
* setDatePanePreferences in the case that the developer desires a
* FilledBorderedPane with a different border, but does not wish to
* overwrite getJPanel. setFillColor cannot be called
*/
if(datePane instanceof FilledBorderedPane){
((FilledBorderedPane)(datePane)).setFillColor
(month.getBackgroundColor());
}
setDatePanePreferences();
panel1.add(datePane); //add the datePane to upper panel, panel1
}
//mode 1 or 2 indicates that time fields will be present
if ((myMode == TIME_MODE) || (myMode == DATE_TIME_MODE)){
//set the JBorderPane that will hold the timefields
timePane =createJPanel();
char ach; //a char to get individual elements of format
int g; //an indexer to run through format
ach = format[0];
g = 0;
/* the format char[] will contain 'h' if the clock for the given Locale
* is a twelve hour clock, or 'H' if the clock is a 24 hour clock. these
* statements iterate through format determine if the Locale's clock is
* 12 or 24 hour, then set class wide boolean
*/
while((ach != 'h') && (ach != 'H')){
g++;
ach = format[g];
}
if(ach == 'h'){
TwelveHourClock = true;
}
else{
TwelveHourClock = false;
}
/* timeOrder is an integer derived from parsing the format char[]
* below is the list of values and corresponding field orderings:
*
* 0: hours, minutes, ampm
* 1: hours, ampm, minutes
* 2: minutes, hours, ampm
* 3: minutes, ampm, hours
* 4: ampm, hours, minutes
* 5: ampm, minutes, hours
*/
timeOrder = OrderParser('h','H','m','a');
hourminute = SeparatorParser('h','H','m'); //get String between h and m
SpinnerLinker linker2 = new SpinnerLinker(null,null,null);
/* given the timeOrder,add hours,minutes,and ampm to the timePane in the
* correct order with the correct String Separators between them. also
* the order of the Spinners for the SpinnerLinker, note that ampm is
* only added for Loacles witha 12 hour clock
*/
switch(timeOrder){
case 0:
addHour(String.copyValueOf(hourminute));
addMinute(null);
if(TwelveHourClock){
addAMPM();
}
linker2 = new SpinnerLinker(hours,minutes,ampm);
break;
case 1:
addHour(null);
if(TwelveHourClock){
addAMPM();
}
addMinute(null);
linker2 = new SpinnerLinker(hours,ampm,minutes);
break;
case 2:
addMinute(String.copyValueOf(hourminute));
addHour(null);
if(TwelveHourClock){
addAMPM();
}
linker2 = new SpinnerLinker(minutes,hours,ampm);
break;
case 3:
addMinute(null);
if(TwelveHourClock){
addAMPM();
}
addHour(null);
linker2 = new SpinnerLinker(minutes,ampm,hours);
break;
case 4:
if(TwelveHourClock){
addAMPM();
}
addHour(String.copyValueOf(hourminute));
addMinute(null);
linker2 = new SpinnerLinker(ampm,hours,minutes);
break;
case 5:
if(TwelveHourClock){
addAMPM();
}
addMinute(String.copyValueOf(hourminute));
addHour(null);
linker2 = new SpinnerLinker(ampm,minutes,hours);
break;
}
/*set the SpinnerLinker to listen to hours and minutes, and ampm if the
*Locale uses a 12 hour clock. Also calls addClock() (see below).
*/
hours.addKeyListener(linker2);
minutes.addKeyListener(linker2);
if(TwelveHourClock){
ampm.addKeyListener(linker2);
addClock();
}
//same as with datePane (see above)
if(timePane instanceof FilledBorderedPane){
((FilledBorderedPane)(timePane)).setFillColor
(hours.getBackgroundColor());
}
setTimePanePreferences();
panel1.add(timePane); //add the datePane to upper panel, panel1
}
add(panel1); //upper panel, panel1 to DateChooser
// add lower panel, panel2, but only if there is anything in the panel
if((myStyle == LARGE) && ((myMode != TIME_MODE) || (TwelveHourClock))){
add(panel2);
}
}
/*this function is designed to parse the char[] format, and return an int
*corresponding to the order of the elements (see timeOrder and dateOrder
*above for translations of ints to orders. this function takes four args:
*one for each of the letters being searched for in the parsing (a, b, c) and
*one (A) because the hours in format may be expressed as 'h' or 'H'. below
*is a translation of the order the args occur in to the int returned:
*
* 0: (a or A), b, c
* 1: (a or A), c, b
* 2: b, (a or A), c
* 3: b, c, (a or A)
* 4: c, (a or A), b
* 5: c, b, (a or A)
*/
private int OrderParser(char a, char A, char b, char c){
int q;
int block;
int offset;
char ch1;
ch1 = format[0];
q = 0;
while((ch1 != a) && (ch1 != A) && (ch1 != b) && (ch1 != c)){
q++;
ch1 = format[q];
}
q++;
if(ch1 == c) {
block = 2;
offset = subParser(a,A,b,q);
}
else if(ch1 == b){
block = 1;
offset = subParser(a,A,c,q);
}
else{
block = 0;
offset = subParser(b,b,c,q);
}
return (2*block + offset);
}
/*this function is designed to parse the char[] format and return a char[]
*which corresponds to the chars separating the elements (a or A) and b. again
*the need for the extra arg A is the result of the fact that hours may be
*represented as 'h' or 'H'.
*/
private char[] SeparatorParser(char a, char A, char b){
int q = 0;
int count = 0;
char ch1,ch2;
char[] storage = new char[format.length];
char[] toReturn;
ch1 = format[q];
while((ch1 != a) && (ch1 !=A) && (ch1 != b)){
q++;
ch1 = format[q];
}
ch2 = ch1;
q++;
ch1 = format[q];
while(ch2 == ch1){
q++;
ch1 = format[q];
}
if(ch2 == b){
while((ch1 != a) && (ch1 != A)){
storage[count] = ch1;
count++;
q++;
ch1 = format[q];
}
}
else{
while(ch1 != b){
storage[count] = ch1;
count++;
q++;
ch1 = format[q];
}
}
toReturn = new char[count];
for(q=0; q<count; q++){
toReturn[q] = storage[q];
}
return toReturn;
}
/*this function is a helper function for OrderParser (see above).this function
*is designed to return an int corresponding to the order of (a or A) and b.
*again the extra arg A is the result of the fact that hours may be expressed
*as 'h' or 'H'. the int arg is the index in format that the search will begin
*at. below is a translation from the int returned to the order or the args:
*
* 0: (a or A), b
* 1: b, (a or A)
*/
private int subParser(char a, char A, char b, int q)
{
char ch1;
ch1 = format[q];
while((ch1 != a) && (ch1 != A) && (ch1 != b)){
q++;
ch1 = format[q];
}
if(ch1 == b){
return 1;
}
else{
return 0;
}
}
/*these add* functions are designed to instatiate the appropriate Spinner,
*and set some of its inherent properties (those that are not subject to
*change due to developer customization. these pieces of codewere put into
*functions because they occurred in different orders depending on the
*dateOrder/timeOrder int. note that all Spinners are instantiated via the
*protected funtion createSpinner()and createStringSpinner. this is to allo
*developers to subclass the Spinner class, and then have that subclass added
*as the Spinner fields in the DateChooser
*/
private void addMonth(String Separator){
months=new String[12];
bigmonths = new String[12];
bigmonths = dfd.getMonths();
switch (myStyle) {
case TINY:
months = new String[] {
"01", "02", "03", "04", "05", "06",
"07", "08", "09", "10", "11", "12"
};
break;
case SMALL:
months = dfd.getShortMonths();
break;
default:
months = dfd.getMonths();
break;
}
String t = months[months.length - 1];
if (t == null || t.length() <= 0) {
// workaround jdk1.1 bug
String n[] = new String[months.length - 1];
System.arraycopy(months, 0, n, 0, n.length);
months = n;
}
month = createStringSpinner(0,Separator, months);
month.setMaximum(11);
month.setValue(cal.get(Calendar.MONTH));
datePane.add(setMonthPreferences());
month.addAdjustmentListener(this);
}
private void addYear(String Separator){
year = createSpinner(1997,Separator);
year.setMinimum(1);
year.setMaximum(10000);
year.setValue(cal.get(Calendar.YEAR));
datePane.add(setYearPreferences());
year.addAdjustmentListener(this);
}
private void addDate(String Separator){
day = createSpinner(0,Separator);
if (myStyle == TINY){
day.setLeadingPad(0);
}
day.setMinimum(1);
day.setMaximum(getDaysInMonth(cal.get(Calendar.MONTH),
cal.get(Calendar.YEAR)));
day.setDigits(2);
day.setValue(cal.get(Calendar.DATE));
datePane.add(setDayPreferences());
day.addAdjustmentListener(this);
}
private void addMiniCal(){
panel1.add(minical);
if ((myStyle == SMALL) || (myStyle == MEDIUM)){
myCalendarButton = new JButton("CAL");
myCalendarButton.addActionListener(this);
datePane.add(myCalendarButton);
}
if (myStyle == LARGE){
minical.showMeTheMoney();
panel2.add(minical.miniPanel);
}
}
private void addHour(String Separator){
int p;
hours =createSpinner(0,Separator);
if(TwelveHourClock){
hours.setMinimum(1);
hours.setMaximum(12);
}
else{
hours.setMinimum(0);
hours.setMaximum(23);
}
hours.setDigits(2);
hours.setValue(cal.get(Calendar.HOUR));
timePane.add(setHourPreferences());
}
private void addMinute(String Separator){
int i;
minutes = createSpinner(0,Separator);
minutes.setMinimum(0);
minutes.setMaximum(59);
minutes.setDigits(2);
minutes.setValue(cal.get(Calendar.MINUTE));
timePane.add(setMinutePreferences());
}
private void addAMPM(){
String ampms[] = dfd.getAmPmStrings();
ampm = createStringSpinner(0,null,ampms);
ampm.setValue(cal.get(Calendar.AM_PM));
timePane.add(setAMPMPreferences());
}
private void addClock(){
if ((myStyle == MEDIUM) || (myStyle == SMALL)){
clockface = new ClockFace();
myClockButton= new JButton("CLOCK");
myClockButton.addActionListener(this);
timePane.add(myClockButton);
ClockPop = new JPopupMenu();
ClockPop.setLayout(new BorderLayout());
ClockPop.add(clockface, BorderLayout.CENTER);
JButton done = new JButton("Done");
done.addActionListener(this);
ClockPop.add(done, BorderLayout.SOUTH);
}
if(myStyle == LARGE){
clockface = new ClockFace();
panel2.add(clockface);
}
}
/*these two functions are used when instantiating the Spinner fields in the
*DateChooser, and are overwritable in a DateChooser subclass to allow
*developers to add extra functionality by subclassing a Spinner and having
*that subclass added as the fields in the DateChooser
*/
/*protected*/private Spinner createSpinner(int startValue, String text){
return new Spinner(startValue,text);
}
/*protected*/private StringSpinner createStringSpinner(int startValue, String text,
String[] names){
return new StringSpinner(startValue,text,names);
}
/*these functions are used in setting the default preferneces for the Spinner
*fields in the DateChooser. they are overwritable in a DateChooser subclass
*to allow developers to customize the properties of the individual Spinner
*fields of the DateChooser. it should be noted that all the set*Preferences
*functions return a JComponent. the reason for this is the possible need to
*wrap the Spinner field within another JComponent and have that JComponent
*added to the datePane/timePane instead of the Spinner itself. a good example
*of this is the setMonthPreferences, which returns a JComboBox in a MEDIUM
*style.
*/
/*protected*/private JComponent setDayPreferences(){
day.setWrap(true);
day.setBorder(new EmptyBorder(0,0,0,0));
return day;
}
/*protected*/private JComponent setMonthPreferences(){
month.setWrap(true);
month.setBorder(new EmptyBorder(0,0,0,0));
if(myStyle == MEDIUM){
int i;
JComboBox combo = new JComboBox();
for(i = 0; i<12; i++){
combo.addPossibleValue((Object)(months[i]));
}
combo.setCurrentValueIndex(month.getValue());
combo.setEditable(true);
SyncTypeComboBoxEditor editor = new SyncTypeComboBoxEditor(combo);
editor.setEditorComponent(month);
combo.setEditor(editor);
return combo;
}
return month;
}
/*protected*/private JComponent setYearPreferences(){
year.setWrap(false);
year.setBorder(new EmptyBorder(0,0,0,0));
return year;
}
/*protected*/private JComponent setHourPreferences(){
hours.setWrap(true);
hours.setBorder(new EmptyBorder(0,0,0,0));
if((myStyle == MEDIUM) || ((myStyle >= MEDIUM) && !(TwelveHourClock))) {
int i,j;
j = hours.getMaximum()+1;
JComboBox combo = new JComboBox();
for(i = hours.getMinimum(); i<j; i++){
combo.addPossibleValue((Object)(Integer.toString(i)));
}
combo.setCurrentValueIndex(hours.getValue()-hours.getMinimum());
combo.setEditable(true);
SyncTypeComboBoxEditor editor = new SyncTypeComboBoxEditor(combo);
editor.setEditorComponent(hours);
combo.setEditor(editor);
return combo;
}
return hours;
}
/*protected*/private JComponent setMinutePreferences(){
minutes.setWrap(true);
minutes.setLeadingPad(0);
minutes.setBorder(new EmptyBorder(0,0,0,0));
if((myStyle == MEDIUM) || ((myStyle >= MEDIUM) && !(TwelveHourClock))) {
int j;
j = minutes.getMaximum()+1;
JComboBox combo = new JComboBox();
String minuteString;
for (int i=0; i<60; i++){
if (i<10){
minuteString = ("0" + Integer.toString(i));
}
else{
minuteString = Integer.toString(i);
}
combo.addPossibleValue((Object)(minuteString));
}
combo.setCurrentValueIndex(minutes.getValue());
combo.setEditable(true);
SyncTypeComboBoxEditor editor = new SyncTypeComboBoxEditor(combo);
editor.setEditorComponent(minutes);
combo.setEditor(editor);
return combo;
}
return minutes;
}
/*protected*/private JComponent setAMPMPreferences(){
ampm.setWrap(true);
ampm.setBorder(new EmptyBorder(0,0,0,0));
return ampm;
}
/*this function is used when instatiating the JPanels datePane and
*timePane. it is overwritable in a DateChooser subclass to account for the
*need for some extra functionality in a JPanel not provided in the
*default settings of the DateChooser. it should be noted that the
*default return value is a FilledBorderedPane, in the construction of which
*the layout is set. because of timing issues, it is neccesary that the layout
*for the returned JPanel be set here. this is pointed out in the
*DateChooser spec and API.
*/
/*protected*/private JPanel createJPanel(){
return new FilledBorderedPane();
}
protected static Border paneBorder = new BevelBorder(1);
/*these set*Preferences functions set the default properties for the two
*JPanels datePane and timePane. they are overwritable in a DateChooser
*subclass to allow these preferences to be customizable
*/
/*protected*/private void setDatePanePreferences(){
datePane.setBorder(paneBorder);
}
/*protected*/private void setTimePanePreferences(){
timePane.setBorder(paneBorder);
}
/*these two enable functions are designed to add or remove the pop-up button
*(or permanent panel) of the appropriate component. this is to allow further
*diversity in the DateChooser, so that these components may be removed if
*they are unwanted or added if they are.
* /
public void setCalendarEnable(boolean enable){
if(enable){
if (minical == null){
addMiniCal();
}
}
else{
if (minical != null){
if ((myStyle == MEDIUM) || (myStyle == SMALL)){
panel1.remove(myCalendarButton);
}
else if (myStyle == LARGE){
panel2.remove(minical.miniPanel);
}
minical = null;
}
}
}
public void setClockEnable(boolean enable){
if(enable){
if (clockface== null){
addClock();
}
}
else{
if (clockface != null){
if ((myStyle == MEDIUM) || (myStyle == SMALL)){
panel1.remove(myClockButton);
}
else if (myStyle == LARGE){
panel2.remove(clockface);
}
clockface = null;
}
}
}
*/
/*an accesibilty function which returns the string representation of the
*the Calendar cal, which is storing the time and date data.
*/
public String toString() {
return getDate().toString();
}
//set the Calendar and the UI with a Date or Calendar Object
public void setDate(Date v) { cal.setTime(v); cal2UI(); }
public void setDate(Calendar v) { setDate(v.getTime()); }
public void setDate(Object v){
if (v instanceof Date) setDate((Date)v);
else if (v instanceof Calendar) setDate(((Calendar)v).getTime());
else throw new IllegalArgumentException();
}
//returns a Date Object storing the values of the DateChooser
public Date getDate() {
UI2cal();
return cal.getTime();
}
/*this function is designed to set the values in the Calendar storage element
*from the values currently in the UI
*/
void UI2cal() {
if ((myMode == DATE_MODE ) || (myMode == DATE_TIME_MODE)){
cal.set(Calendar.YEAR, year.getValue());
cal.set(Calendar.MONTH, month.getValue());
cal.set(Calendar.DATE, day.getValue());
}
if ((myMode == TIME_MODE ) || (myMode == DATE_TIME_MODE)){
cal.set(Calendar.HOUR, hours.getValue());
cal.set(Calendar.MINUTE, minutes.getValue());
if(TwelveHourClock){
cal.set(Calendar.AM_PM, ampm.getValue());
}
}
}
/*this function is designed to set the values in the UI from the values in the
*Calendar storage element.
*/
void cal2UI() {
if ((myMode == DATE_MODE ) || (myMode == DATE_TIME_MODE)){
year.setValue(cal.get(Calendar.YEAR));
month.setValue(cal.get(Calendar.MONTH));
day.setValue(cal.get(Calendar.DATE));
day.setMaximum(getDaysInMonth(month.getValue(),year.getValue()));
}
if ((myMode == TIME_MODE ) || (myMode == DATE_TIME_MODE)){
hours.setValue(cal.get(Calendar.HOUR));
minutes.setValue(cal.get(Calendar.MINUTE));
if(TwelveHourClock){
ampm.setValue(cal.get(Calendar.AM_PM));
}
}
}
/*this function is designed to return the number of days in the given month
*from the given year. it is public because this is a useful function to
*perform when dealing with dates. this almost certainly should have been
*written in GrergorianCalendar.
*/
int getDaysInMonth(int m, int y){
int days = 30;
if (m ==0 || m ==2 || m ==4 || m==6 || m==7 || m==9 || m==11){
days=31;
}
else{
if (m ==1){
days =28;
if (y%400==0 || y%4==0 && y%100!=0){
days +=1;
}
}
}
return days;
}
/*the DateChooser implements AdjustmentListener so that it can monitor its
*Spinner fields for important changes
*/
public void adjustmentValueChanged(AdjustmentEvent e)
{
/*if the month Spinner field wraps over, increment or decrement the year
*field appropriately
*/
if(month.wrapped){
if(month.getValue() == 0){
month.wrapped = false;
year.setValue(year.getValue()+1);
}
else{
month.wrapped = false;
year.setValue(year.getValue()-1);
}
}
/*if the day Spinner field wraps over, increment or decrement the month
*field appropriately
*/
if(day.wrapped){
if(day.getValue() == 1){
day.wrapped = false;
month.setValue(month.getValue()+1);
}
else{
day.wrapped = false;
month.setValue(month.getValue()-1);
day.setValue(day.getMaximum());
}
}
/*if the month Spinner or year Spinner has changed, it is possible that the
*value in the day Spinner field is no longer valid (too high). in this
*event, the day is set to the maximum value for the current month in the
*given year. also, if the minicalendar is visible, the month diplayed
*will change to the appropriate month in the appropriate year.
*/
if((((Spinner)(e.getSource())) == month) || (((Spinner)(e.getSource())) ==
year)){
int max = getDaysInMonth(month.getValue(),year.getValue());
if (day.getValue() > max ){
day.setValue(max);
}
day.setMaximum(max);
if(minical.dcv != null) {
if(minical.dcv.isVisible()) {
UI2cal();
minical.setCalendar();
}
}
}
/*a close look at the add* functions above will reveal that the DateChooser
*is only added as an AdjustmentListener to the day,month,and year Spinners.
*thus this else implies that the day Spinner has changed. in the case that
*the minicalendar is showing, this will highlight the appropriate date on
*the minicalendar.
*/
else{
if(minical.dcv != null) {
if(minical.dcv.isVisible()) {
UI2cal();
minical.dcv.highlightDate(day.getValue());
}
}
}
}
/*the DateChooser implements ActionListener so that it can monitor its pop-up
*buttons.
*/
public void actionPerformed(ActionEvent e) {
/*if the "CAL" button is pushed, make sure the Calendar stoarge element is
*current, then show the minicalendar
*/
if(e.getActionCommand().equals("CAL")){
UI2cal();
minical.showMeTheMoney();
}
/*if the "CLOCK" button is pushed, make sure the Calendar stoarge element is
*current, then show the clockface
*/
else if(e.getActionCommand().equals("CLOCK")){
UI2cal();
ClockPop.show(((JButton)(e.getSource())),0,0);
}
/*this button lives on the ClockFace pop-up, but should be handled here
*because the DateChooser controls the JPopupMenu that contains the
*ClockFace.
*/
else if(e.getActionCommand().equals("Done")){
ClockPop.setVisible(false);
}
}
/*the class minical is for now tied to the DateChooser. based on the Calendar
*storage element, the MiniCal will instantiate a DateCanvas, which is the
*graphics of the MiniCal. the MiniCal also has a JLabel header which
*displays the current month and year, as well as next and previous buttons
*to cycle through months. this class may prove useful as a separate class
*altogether. in order to pull this apart, the mode argument in the
*constructor will need to dealt with in DateChooser itself, and the MiniCal
*components should only be added to a JPanel, not a JPopupMenu. also the
*cancel and ok buttons would have to be moved into the DateChooser itself.
*MiniCal implements ActionListener so that it can monitor its buttons
*/
public class MiniCal extends JComponent implements ActionListener{
private JPanel miniPanel;//used only for a LARGE style DaetChooser
private JButton cancel,ok;//buttons to accept or deny chosen date
private CalButton previous,next;//used to cycle through months
private JLabel MnthYear;//display label for month and year
private DateCanvas.DOWHeader dow;//display for days of the week
private DateCanvas dcv;//class that handles the graphics
private FontMetrics fm;//for computing sizes of labels and graphics
private JPopupMenu f;//used for all styles except LARGE
private Font dateFont;//font for determining FontMetrics
private int saveMonth;//these three are storage for the month, date, and
private int saveDate;//year, in case of a "cancel"
private int saveYear;
private int saveHour; //daylight savings bug fix. Calendar.setTime().
private int myMiniCalMode;//add parts to a Popup or to a JPanel
/*the constructor for the MiniCal is light because it is constructed in the
*DateChooser regardless of whether or not it gets used.
*/
public MiniCal(int mode) {
/*store the mode. modes and meanings:
*
* 0: components of MiniCal will be added to a JPopupMenu (f), cancel and
* ok buttons will be used
* 1: components of MiniCal will be added to a JPanel (miniPanel), cancel
* and ok buttons will not be used.
*
*/
myMiniCalMode = mode;
}
/*this is the real instantiation of the MiniCal, and occurs only when the
*the DateChooser requests to display the MiniCal
*/
public void showMeTheMoney(){
/*if the mode is 0, store the date, month and year information in case of
*a "cancel". construct the JPopupMenu.
*/
if(myMiniCalMode == 0){
saveMonth = cal.get(Calendar.MONTH);
saveDate = cal.get(Calendar.DATE);
saveYear = cal.get(Calendar.YEAR);
f = new JPopupMenu();
}
//else the mode is 1, construct the JPanel (miniPanel) and set its layout
else{
miniPanel = new JPanel(false);
miniPanel.setLayout(new BorderLayout());
}
//construct the cycling buttons, add MiniCal as the ActionListener
previous = new CalButton(this,"<",0);
next = new CalButton(this,">",1);
previous.addActionListener(this);
next.addActionListener(this);
/*the date for the current month must initially be set to 1 for purposes
*of calculating how and where to draw the graphics.
*/
cal.set(Calendar.DATE, 1);
/*this is a quick hack around the problem that arises when crossing
*daylight savings time in Calendar and then calling setTime(). this is
*something that should definitely be fixed in Calendar
*/
saveHour = cal.get(Calendar.HOUR);
/*recalculate the Calendar values with the DATE now set at 1. if the hour
*changes, set it back to what it was.
*/
cal.setTime(cal.getTime());
if(cal.get(Calendar.HOUR) != saveHour){
cal.set(Calendar.HOUR,saveHour);
}
//set the month and year label, align it
MnthYear= new JLabel((bigmonths[cal.get(Calendar.MONTH)]) + " " +
((new Integer(0)).toString(cal.get(Calendar.YEAR))));
MnthYear.setHorizontalAlignment(MnthYear.CENTER);
/*conctruct the DateCanvas. see DateCanvas for more details about its
*constructor, and the args it takes. quickly, these args are: an offset
*(what day of the week to start drawing days on), the number of days in
*the month, the month, and the year.
*/
dcv = new DateCanvas(cal.get(Calendar.DAY_OF_WEEK)-1,getDaysInMonth
(cal.get(Calendar.MONTH), cal.get(Calendar.YEAR)));
/*two JPanels, p1 and p2 are used to layout the MiniCal. there's probably
*a better way to do this. p1 contains the month-year label and the
*cycling buttons. p2 contains the days of the week label, and the
*DateCanvas.
*/
JPanel p1 = new JPanel(false);
p1.setLayout(new BorderLayout());
p1.add(previous, BorderLayout.WEST);
p1.add(MnthYear, BorderLayout.NORTH);
p1.add(next, BorderLayout.EAST);
JPanel p2 = new JPanel(false);
p2.setLayout(new BorderLayout());
p2.add(dow, BorderLayout.NORTH);
p2.add(dcv, BorderLayout.CENTER);
/*if the mode =0, add p1 and p2 to the JPopupMenu (f), as well as the
*cancel and ok buttons.
*/
if(myMiniCalMode == 0){
cancel = new JButton("Cancel");
ok = new JButton("OK");
cancel.addActionListener(this);
ok.addActionListener(this);
JPanel p3 = new JPanel(false);
p3.setLayout(new BorderLayout());
p3.add(cancel, BorderLayout.WEST);
p3.add(ok, BorderLayout.EAST);
f.setLayout(new BorderLayout());
f.add(p1, BorderLayout.NORTH);
f.add(p2, BorderLayout.CENTER);
f.add(p3, BorderLayout.SOUTH);
f.show(myCalendarButton,0,0);
}
/*else the mode is 1, p1 and p2 are added to the miniPanel. cancel and ok
*are not used here.
*/
else{
miniPanel.add(p1, BorderLayout.NORTH);
miniPanel.add(p2, BorderLayout.CENTER);
}
}
/*MiniCal implements ActionListener so that it can monitor its buttons:
*cycling, cancal and ok.
*/
public void actionPerformed(ActionEvent e) {
//go to the previous month
if(e.getActionCommand().equals("<")){
goBack();
}
//go to the next month
else if(e.getActionCommand().equals(">")){
goForward();
}
/*user has decided not to use values, reset Calendar values from storage,
*reset the UI and hide the JPopupMenu
*/
else if(e.getActionCommand().equals("Cancel")){
cal.set(Calendar.MONTH,saveMonth);
cal.set(Calendar.DATE,saveDate);
cal.set(Calendar.YEAR,saveYear);
cal2UI();
f.setVisible(false);
}
//values in Calendar are already set; set the UI, hide the JPopupMenu
else if(e.getActionCommand().equals("OK")){
cal2UI();
f.setVisible(false);
}
}
/*this function is designed to set the dateCanvas to the next month. it does
*wrapping. the last line setCalendar() calls a function that actually
*resets the Calendar values and repaints the Datecanvas
*/
public void goForward(){
if ((cal.get(Calendar.MONTH)+1) > 11 ){
cal.set(Calendar.YEAR, cal.get(Calendar.YEAR)+1);
cal.set(Calendar.MONTH, 0);
day.setMaximum(31);
}
else{
cal.set(Calendar.MONTH, cal.get(Calendar.MONTH)+1);
day.setMaximum(getDaysInMonth(cal.get(Calendar.MONTH),cal.get
(Calendar.YEAR)));
}
setCalendar();
}
//same as goForward(), just in the other direction
public void goBack(){
if ((cal.get(Calendar.MONTH)-1) < 0 ){
cal.set(Calendar.YEAR, cal.get(Calendar.YEAR)-1);
cal.set(Calendar.MONTH, 11);
day.setMaximum(31);
}
else{
cal.set(Calendar.MONTH, cal.get(Calendar.MONTH)-1);
day.setMaximum(getDaysInMonth(cal.get(Calendar.MONTH),cal.get
(Calendar.YEAR)));
}
setCalendar();
}
/*this function is designed to reset all the necesary aspects of the MiniCal
*when a change occurs. it does most of the same setting and error trapping
*as seen in showMeTheMoney()
*/
public void setCalendar(){
MnthYear.setText((bigmonths[cal.get(Calendar.MONTH)]) + " " +
((new Integer(0)).toString(cal.get(Calendar.YEAR))));
cal.set(Calendar.DATE, 1);
saveHour = cal.get(Calendar.HOUR);
cal.setTime(cal.getTime());
if(cal.get(Calendar.HOUR) != saveHour){
cal.set(Calendar.HOUR,saveHour);
}
dcv.ChangeMonth(cal.get(Calendar.DAY_OF_WEEK)-1,getDaysInMonth
(cal.get(Calendar.MONTH),cal.get(Calendar.YEAR)));
}
/*this subclass of the MiniCal handles the painting and sizing of the
*graphical calendar in MiniCal.
*/
class DateCanvas extends JComponent {
private int myOffset;//for which day of the week the first is on
private int myTotal;//total days in the month
private Graphics g2;//an extra copy of the Graphics, to alleviate too many
//calls to getGraphics()
private boolean thisMonth;//whether this month has been clicked on yet
private boolean mouseDown;//boolean for highlight dragging
private int insetH;//horizontal inset, space before drawing
private int insetV;//vertical inset+letterH, includes inset and letterH
private int letterW;//width of a letter, gotten by fm
private int letterH;//height of a letter, gotten by fm
private int rectW;//width of painted canvas
private int rectH;//height of painted canvas
private int X,Y;//coordinates of highlighted date
private int oldX, oldY;//coordinates of previously highlighted date
private int StartX, StartY;//coordinated of most recent mouse event
private int GRIDX, GRIDY;//basically indices to the array of dates
private int date;//the current date highlighted
private Dimension CanvasDimension;//dimension for DateCanvas
/*constructor takes offset (what day of the week the first day of the
*month is on, and total, the number of days in the month
*/
public DateCanvas(int offset,int total) {
//set some initial values
myOffset = offset;
myTotal = total;
thisMonth = false;
mouseDown = false;
dow = new DOWHeader();//instaniate a new days of the week header
/*perhaps these should be switched over to MouseListener and
*MouseMotionListener. these are to listen for click and dragging on the
*DateCanvas, for date highlighting and selection purposes.
*/
enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK |
AWTEvent.MOUSE_EVENT_MASK);
}
/*some font tricks to make sure the font is actually set before the size
*of the DateCanvas is determined. this is jag's code.
*/
public void setFont(Font f) {
if (f != getFont()){
super.setFont(f);
CanvasDimension = null;
invalidate();
}
}
/*calculates the size of the DateCanvas based on FontMetrics. all
*calculations are pretty straightforward.
*/
public Dimension getMinimumSize(){
if (CanvasDimension == null){
fm = getFontMetrics(getFont());
letterW = fm.charWidth('8');
letterH = fm.getHeight();
insetH = letterW / 2;
insetV = letterH/2 + letterH;
rectW = 2*insetH + 3*daysInWeek*letterW;
rectH = 2*(insetV-letterH) + ((int)(1.5*letterH*6));
CanvasDimension = new Dimension(rectW + 2, rectH + 2);
}
return CanvasDimension;
}
//paint this bad boy!
public void paint(Graphics g){
//make sure the size has been determined before painting
if (CanvasDimension == null){
getMinimumSize();
}
//white background
g.setColor(Color.white);
g.fillRect(1,1, rectW-2, rectH-2);
//black border
g.setColor(Color.black);
g.drawRect(0,0, rectW, rectH);
/*this is to calculate the date squares that show up on the DateCanvas
*but are disables because they come from the previous month
*/
int i,row,x,disabled;
x=0;
row = 0;
//get previous month
if(cal.get(Calendar.MONTH) == 0){
disabled = getDaysInMonth(11,cal.get(Calendar.YEAR));
}
else{
disabled = getDaysInMonth(cal.get(Calendar.MONTH) -1,
cal.get(Calendar.YEAR));
}
/*draw date strings from current month in appropriate places. note that
*each square in letterW*3 wide and letterH*1.5 high.
*/
for(i = 1; i<=myTotal; i++){
x = (i+myOffset)-(daysInWeek*row) - 1;
if (x > 6){
row++;
x=0;
}
g.drawString(Integer.toString(i),insetH + 3*letterW*x+2, insetV +
((int)(1.5*letterH))*row);
}
/*give the disabled look to all squares except those which the current
*month's dates are on.this is done by clearing out the white background
*/
g.clearRect(insetH,insetV-letterH, myOffset*letterW*3,
((int)(letterH*1.5)));
g.clearRect(insetH+ 3*letterW*(x+1), insetV - letterH +
((int)(1.5*letterH))*row, 3*letterW*(daysInWeek-x-1),
((int)(1.5*letterH*(6-row))));
g.clearRect(insetH, insetV-letterH+((int)(1.5*letterH))*(row+1),
3*letterW*(x+1), ((int)(1.5*letterH))*(6-row-1));
//these two loops graw the grid lines on the DateCanvas
for(i=0; i<=daysInWeek; i++){
g.drawLine(insetH+letterW*3*i, insetV-letterH, insetH+letterW*3*i,
insetV-letterH+9*letterH);
}
for(i=0; i<7; i++){
g.drawLine(insetH, insetV-letterH+((int)(letterH*1.5*i)),
insetH+21*letterW,insetV-letterH+((int)(letterH*1.5*i)));
}
//draw strings for previous month's dates (disabled)
for(i=disabled-myOffset+1; i<=disabled; i++){
g.drawString(Integer.toString(i),insetH +
3*letterW*(i-disabled+myOffset-1)+2, insetV);
}
//reset disabled int to dates in next month
disabled = daysInWeek*6 - (row*daysInWeek + x);
//draw strings for next month's dates (disabled)
for(i=1; i<disabled; i++){
x++;
if (x > 6){
row++;
x=0;
}
g.drawString(Integer.toString(i),insetH + 3*letterW*x+2, insetV +
((int)(1.5*letterH))*row);
}
/*make a copy of DateCanvas Graphics, to alliviate multiple calls to
*getGraphics()
*/
if(g2 == null){
g2 = g.create();
}
//highlight current date
highlightDate(day.getValue());
}
//minimumSize is the preferred size
public Dimension getPreferredSize() {
return getMinimumSize();
}
/*a helper function used when the month is changed, to avoid writing these
*statements everwhere that these values need to be set.
*/
public void ChangeMonth(int offset, int total){
myOffset = offset;
myTotal = total;
paint(g2);
}
//neccessary in case the DateCanvas becomes obcured by another window
public void update(Graphics g){
paint(g);
}
/*this function is designed to calculate the date on the DateCanvas given
*cooordinates x and y. the steps of the calculation are commented below
*to facillitate understanding.
*/
public int getDateOnComponent(int x, int y){
//GRIDX = (int)((x - insetH)/(letterW*3));
//GRIDY = (int)((y - insetV + letterH)/(1.5*letterH));
//DATE = (GRIDX - myOffset) + 1 + (daysInWeek*GRIDY);
return (((int)((x - insetH)/(letterW*3))) - myOffset) + 1 +
(daysInWeek*((int)((y - insetV + letterH)/(1.5*letterH))));
}
/*this function, used mostly in conjuction with the DateChooser in
*response to an Adjustment change in hte day Spinner, is designed to
*highlight the passed date. calculations translating the date into
*coordinates on the DateCanvas are stepped throug in comments below.
*/
public void highlightDate(int newDate){
//(date+Offset-1)%daysInWeek = GRIDX (see above)
//(date+Offset-1)/daysInWeek-1 = GRIDY (see above)
highlightDate(((newDate+myOffset-1)%7)*(letterW*3)+insetH+2,
((newDate+myOffset-1)/7-1)*((int)(1.5*letterH))+
insetV+letterH);
}
//draw a highlight for the date at the given coordinates
public void highlightDate(int tempX, int tempY){
//get the date, and check that it is in bounds
date = getDateOnComponent(tempX, tempY);
if ((date > 0) && (date <= getDaysInMonth((cal).get(Calendar.MONTH),
(cal).get(Calendar.YEAR))) && (tempX < rectW - insetH - 1) &&
(tempX > insetH+1)){
cal.set(Calendar.DATE,date);
if(thisMonth){
oldX = X;
oldY = Y;
GRIDX = (int)((oldX - insetH)/(letterW*3));
GRIDY = (int)((oldY - insetV + letterH)/(1.5*letterH));
StartX = GRIDX*letterW*3+insetH;
StartY = (int)(GRIDY*1.5*letterH) + insetV - letterH;
g2.setColor(Color.white);
g2.fillRect(StartX+1, StartY+1, letterW*3-1,
(int)(letterH*1.5)-1);
g2.setColor(Color.black);
g2.drawString((new Integer(0)).toString
(getDateOnComponent(oldX,oldY)),insetH +
3*letterW*GRIDX+2,insetV +
((int)(1.5*letterH))*GRIDY);
}
else{
thisMonth = true;
}
X = tempX;
Y = tempY;
GRIDX = (int)((X - insetH)/(letterW*3));
GRIDY = (int)((Y - insetV + letterH)/(1.5*letterH));
StartX = GRIDX*letterW*3+insetH;
StartY = (int)(GRIDY*1.5*letterH) + insetV - letterH;
g2.setColor(new Color(0,0,150));
g2.fillRect(StartX + 1, StartY + 1, letterW*3 - 1,
(int)(letterH*1.5) - 1);
g2.setColor(Color.white);
g2.drawString((new Integer(0)).toString(date),insetH +
3*letterW*GRIDX+2,insetV +
((int)(1.5*letterH))*GRIDY+1);
g2.setColor(Color.black);
}
}
protected void processMouseEvent(MouseEvent e){
if (e.getID()==e.MOUSE_PRESSED) {
mouseDown = true;
int tempX = e.getX();
int tempY = e.getY();
highlightDate(tempX,tempY);
}
else if (e.getID()==e.MOUSE_RELEASED) {
mouseDown = false;
if ((date > 0) && (date <= getDaysInMonth((cal).get(Calendar.MONTH),
(cal).get(Calendar.YEAR)))){
cal.set(Calendar.DATE,date);
}
if(myMiniCalMode == 1){
cal2UI();
}
}
}
protected void processMouseMotionEvent(MouseEvent e){
if (mouseDown) {
int tempX = e.getX();
int tempY = e.getY();
if ((GRIDX != (int)((tempX - insetH)/(letterW*3)) ||
(GRIDY != (int)((tempY - insetV + letterH)/(1.5*letterH))))){
highlightDate(tempX,tempY);
}
}
}
class DOWHeader extends JComponent{
private String[] DOW;
private Dimension DOWDimension;
public DOWHeader(){
char[] three = new char[3];
char[] two = new char[2];
DOW = new String[daysInWeek + 1];
DOW = dfd.getShortWeekdays();
int i;
for (i=1; i<daysInWeek + 1; i++){
three = (DOW[i]).toCharArray();
two[0] = three[0];
if((two.length > 1) && (three.length > 1)){
two[1] = three[1];
}
DOW[i] = String.copyValueOf(two);
}
}
public void setFont(Font f) {
if (f != getFont()){
super.setFont(f);
DOWDimension = null;
invalidate();
}
}
public Dimension getMinimumSize(){
if (DOWDimension == null){
fm = getFontMetrics(getFont());
letterW = fm.charWidth('8');
letterH = fm.getHeight();
insetH = letterW / 2;
insetV = letterH/2 + letterH;
rectW = 2*insetH + 3*daysInWeek*letterW;
rectH = 2*(insetV-letterH) + ((int)(1.5*letterH*6));
DOWDimension = new Dimension(insetH + letterW*3*daysInWeek, insetV);
}
return DOWDimension;
}
public void paint(Graphics g){
if (DOWDimension == null){
getMinimumSize();
}
g.setColor(Color.black);
int i;
for(i = 0; i<daysInWeek; i++){
g.drawString(DOW[i+1],insetH + letterW*3*i,insetV);
}
}
public Dimension getPreferredSize() {
return getMinimumSize();
}
}
}
}
class ClockFace extends JComponent implements AdjustmentListener {
Dimension d = new Dimension();
private Image img;
int x0, y0;
public ClockFace(){
hours.addAdjustmentListener(this);
minutes.addAdjustmentListener(this);
enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
final byte[] imageBytes = {
(byte)0x47, (byte)0x49, (byte)0x46, (byte)0x38, (byte)0x39, (byte)0x61,
(byte)0x50, (byte)0x0, (byte)0x50, (byte)0x0, (byte)0xb3, (byte)0x0,
(byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x80, (byte)0x0,
(byte)0x0, (byte)0x0, (byte)0x80, (byte)0x0, (byte)0x80, (byte)0x80,
(byte)0x0, (byte)0x0, (byte)0x0, (byte)0x80, (byte)0x80, (byte)0x0,
(byte)0x80, (byte)0x0, (byte)0x80, (byte)0x80, (byte)0x80, (byte)0x80,
(byte)0x80, (byte)0xc0, (byte)0xc0, (byte)0xc0, (byte)0xff, (byte)0x0,
(byte)0x0, (byte)0x0, (byte)0xff, (byte)0x0, (byte)0xff, (byte)0xff,
(byte)0x0, (byte)0x0, (byte)0x0, (byte)0xff, (byte)0xff, (byte)0x0,
(byte)0xff, (byte)0x0, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
(byte)0xff, (byte)0x21, (byte)0xf9, (byte)0x4, (byte)0x9, (byte)0x8,
(byte)0x0, (byte)0xf, (byte)0x0, (byte)0x2c, (byte)0x0, (byte)0x0,
(byte)0x0, (byte)0x0, (byte)0x50, (byte)0x0, (byte)0x50, (byte)0x0,
(byte)0x40, (byte)0x4, (byte)0xfe, (byte)0xf0, (byte)0xc9, (byte)0x49,
(byte)0xab, (byte)0xbd, (byte)0x58, (byte)0xa6, (byte)0x9d, (byte)0xb2,
(byte)0xff, (byte)0x60, (byte)0x38, (byte)0x1, (byte)0x24, (byte)0x20,
(byte)0x72, (byte)0x5d, (byte)0x58, (byte)0x9a, (byte)0x62, (byte)0x2b,
(byte)0xb1, (byte)0x6e, (byte)0x2c, (byte)0x3f, (byte)0xb0, (byte)0xcb,
(byte)0xae, (byte)0xe4, (byte)0xac, (byte)0x67, (byte)0xf8, (byte)0xfd,
(byte)0xd5, (byte)0xbb, (byte)0xa0, (byte)0xb0, (byte)0x86, (byte)0x2a,
(byte)0x6e, (byte)0x82, (byte)0x8c, (byte)0xa4, (byte)0x72, (byte)0xb9,
(byte)0xc, (byte)0x1a, (byte)0x8b, (byte)0x14, (byte)0xa0, (byte)0x70,
(byte)0x2a, (byte)0x93, (byte)0x56, (byte)0x7a, (byte)0x39, (byte)0xea,
(byte)0x14, (byte)0x6b, (byte)0x55, (byte)0x69, (byte)0xb5, (byte)0x5d,
(byte)0x70, (byte)0x29, (byte)0x84, (byte)0x52, (byte)0x8d, (byte)0xbf,
(byte)0xdb, (byte)0x95, (byte)0x86, (byte)0xf3, (byte)0x52, (byte)0xa3,
(byte)0xab, (byte)0x35, (byte)0xb7, (byte)0x38, (byte)0xdb, (byte)0x4e,
(byte)0xbf, (byte)0xef, (byte)0xb4, (byte)0x70, (byte)0x14, (byte)0xcf,
(byte)0xbf, (byte)0x62, (byte)0xf4, (byte)0x3a, (byte)0x4c, (byte)0x82,
(byte)0xc, (byte)0x77, (byte)0x56, (byte)0x80, (byte)0x33, (byte)0x83,
(byte)0x4a, (byte)0x85, (byte)0x3c, (byte)0x7d, (byte)0x8d, (byte)0x34,
(byte)0x2d, (byte)0x87, (byte)0x8e, (byte)0x3f, (byte)0x68, (byte)0x30,
(byte)0x72, (byte)0x54, (byte)0x72, (byte)0x91, (byte)0x92, (byte)0x16,
(byte)0x0, (byte)0x28, (byte)0x74, (byte)0x9a, (byte)0x8d, (byte)0x65,
(byte)0x9f, (byte)0x76, (byte)0x1e, (byte)0xa1, (byte)0x3c, (byte)0x99,
(byte)0x8b, (byte)0x96, (byte)0x19, (byte)0xa5, (byte)0x1e, (byte)0x6a,
(byte)0xa7, (byte)0x55, (byte)0x7f, (byte)0x67, (byte)0x69, (byte)0x9e,
(byte)0x7b, (byte)0x5b, (byte)0xa2, (byte)0x31, (byte)0xae, (byte)0x79,
(byte)0x38, (byte)0xb6, (byte)0x22, (byte)0x58, (byte)0x20, (byte)0x86,
(byte)0xbb, (byte)0x8c, (byte)0x9b, (byte)0x93, (byte)0xc0, (byte)0x97,
(byte)0xc1, (byte)0x6f, (byte)0x89, (byte)0x78, (byte)0x44, (byte)0x4f,
(byte)0x6c, (byte)0x3b, (byte)0x89, (byte)0x82, (byte)0x4e, (byte)0xcb,
(byte)0x47, (byte)0x2f, (byte)0xc7, (byte)0xce, (byte)0x4d, (byte)0x94,
(byte)0x17, (byte)0xb8, (byte)0x2d, (byte)0xcf, (byte)0xc9, (byte)0xc6,
(byte)0xc4, (byte)0x41, (byte)0x80, (byte)0x71, (byte)0xb1, (byte)0xde,
(byte)0x23, (byte)0xe2, (byte)0x91, (byte)0xa9, (byte)0xe3, (byte)0xb0,
(byte)0xb3, (byte)0xbc, (byte)0xe8, (byte)0x7f, (byte)0x5f, (byte)0x86,
(byte)0xe2, (byte)0xdf, (byte)0xf0, (byte)0xd3, (byte)0x8d, (byte)0xba,
(byte)0x20, (byte)0xab, (byte)0xa6, (byte)0xf2, (byte)0xbb, (byte)0xe7,
(byte)0xf, (byte)0xf7, (byte)0x6d, (byte)0xea, (byte)0xec, (byte)0xf6,
(byte)0x98, (byte)0x1, (byte)0xd4, (byte)0xd2, (byte)0x6f, (byte)0xe0,
(byte)0xc, (byte)0x12, (byte)0x9d, (byte)0xb2, (byte)0x8d, (byte)0xdb,
(byte)0x57, (byte)0x70, (byte)0x9f, (byte)0xbe, (byte)0x7c, (byte)0x16,
(byte)0xa, (byte)0x6e, (byte)0x82, (byte)0xd8, (byte)0xc7, (byte)0x61,
(byte)0x44, (byte)0x81, (byte)0x3f, (byte)0x28, (byte)0xbe, (byte)0xa9,
(byte)0xf4, (byte)0x6f, (byte)0x8, (byte)0x1d, (byte)0x85, (byte)0xa2,
(byte)0xeb, (byte)0x26, (byte)0x82, (byte)0x4c, (byte)0x87, (byte)0xed,
(byte)0x5a, (byte)0x37, (byte)0x30, (byte)0xac, (byte)0x8a, (byte)0x19,
(byte)0x1c, (byte)0xf1, (byte)0x6d, (byte)0x65, (byte)0x49, (byte)0x1b,
(byte)0x1a, (byte)0x1, (byte)0x5a, (byte)0xbc, (byte)0xd2, (byte)0x6b,
(byte)0x20, (byte)0x17, (byte)0x2f, (byte)0x51, (byte)0x3a, (byte)0x2,
(byte)0x83, (byte)0x7, (byte)0xce, (byte)0xa5, (byte)0x8d, (byte)0x94,
(byte)0x3e, (byte)0x43, (byte)0xbe, (byte)0xc, (byte)0x2a, (byte)0xd4,
(byte)0xf, (byte)0x9e, (byte)0x6d, (byte)0x8b, (byte)0x86, (byte)0x7e,
(byte)0xa9, (byte)0x96, (byte)0xd4, (byte)0xe8, (byte)0xd2, (byte)0x6a,
(byte)0x8a, (byte)0x4c, (byte)0xf2, (byte)0x8b, (byte)0x96, (byte)0x22,
(byte)0x10, (byte)0xd4, (byte)0x24, (byte)0xd0, (byte)0xa8, (byte)0x52,
(byte)0x45, (byte)0x72, (byte)0x95, (byte)0xd0, (byte)0xe, (byte)0xad,
(byte)0x2c, (byte)0xa9, (byte)0x31, (byte)0xdd, (byte)0xa8, (byte)0xf4,
(byte)0x29, (byte)0x52, (byte)0xa9, (byte)0x4e, (byte)0x89, (byte)0xfa,
(byte)0x3a, (byte)0xa9, (byte)0xb6, (byte)0x6c, (byte)0xda, (byte)0xb6,
(byte)0x6e, (byte)0xdf, (byte)0xe6, (byte)0x31, (byte)0xa8, (byte)0xce,
(byte)0x5c, (byte)0xae, (byte)0x99, (byte)0x9f, (byte)0x7a, (byte)0x99,
(byte)0xeb, (byte)0x11, (byte)0xb4, (byte)0xa6, (byte)0xaf, (byte)0x91,
(byte)0xa2, (byte)0x74, (byte)0x2, (byte)0xed, (byte)0x3b, (byte)0x84,
(byte)0xb0, (byte)0x4a, (byte)0x91, (byte)0x80, (byte)0x73, (byte)0x76,
(byte)0x4c, (byte)0x8c, (byte)0x2d, (byte)0x9c, (byte)0x60, (byte)0x38,
(byte)0x9e, (byte)0x1e, (byte)0xd7, (byte)0xc2, (byte)0xbb, (byte)0x46,
(byte)0x5a, (byte)0xc6, (byte)0x1c, (byte)0x8c, (byte)0x21, (byte)0xc5,
(byte)0x9c, (byte)0x20, (byte)0x11, (byte)0xf1, (byte)0xce, (byte)0x3a,
(byte)0xa4, (byte)0x30, (byte)0x36, (byte)0x9e, (byte)0xb7, (byte)0x92,
(byte)0x6f, (byte)0xa8, (byte)0x7a, (byte)0x70, (byte)0x55, (byte)0x89,
(byte)0x4e, (byte)0x1d, (byte)0xd0, (byte)0xb2, (byte)0xa3, (byte)0x8,
(byte)0x0, (byte)0x3b,
};
img = Toolkit.getDefaultToolkit().createImage(imageBytes);
if (img != null){
d.width = img.getWidth(this);
d.height = img.getHeight(this);
}
}
public void adjustmentValueChanged(AdjustmentEvent e) {
if (isShowing())
repaint(50);
}
protected void processMouseMotionEvent(MouseEvent e) {
processMouseEvent(e);
}
protected void processMouseEvent(MouseEvent e) {
switch (e.getID()) {
case e.MOUSE_PRESSED:
case e.MOUSE_DRAGGED:
case e.MOUSE_RELEASED:
float theta = (float) Math.atan2(e.getX() - x0, e.getY() - y0);
int v = 24 - (int) (theta * (12 * 4) / (2 * Math.PI) + 0.5);
if (v < 0)
v += 48;
int nh = v >> 2;
int oh = hours.getValue();
if (oh >= 12)
oh -= 12;
if (nh > 9 && oh < 3 || nh < 3 && oh > 9)
ampm.setValue(1 - ampm.getValue());
hours.setValue(nh);
minutes.setValue((v & 3) * 15);
break;
}
return;
}
public void setBounds(int x, int y,
int width, int height) {
super.setBounds(x, y, width, height);
x0 = width >> 1;
y0 = height >> 1;
}
public Dimension getMinimumSize() {
waitSize();
return d;
}
public Dimension getPreferredSize() {
return getMinimumSize();
}
private synchronized void waitSize() {
if (img != null)
try {
while (d.width < 0 || d.height < 0)
wait();
} catch(InterruptedException i) {
}
}
public synchronized boolean imageUpdate(Image img,
int infoflags,
int x, int y,
int width, int height) {
if ((infoflags & (ERROR | ABORT)) != 0) {
img = null;
d.width = 100;
d.height = 100;
notifyAll();
return false;
}
repaint(10);
if ((infoflags & WIDTH) != 0)
d.width = img.getWidth(this);
if ((infoflags & HEIGHT) != 0)
d.height = img.getHeight(this);
notifyAll();
return (infoflags & ALLBITS) == 0;
}
private void drawHand(Graphics g, int v,
float scale, boolean broad) {
float theta = (float) (v * (2 * Math.PI / 120));
scale *= x0 < y0 ? x0 : y0;
int st = (int) (Math.sin(theta) * scale);
int ct = (int) (Math.cos(theta) * scale);
if (!broad)
g.drawLine(x0, y0, x0 + st, y0 - ct);
else {
int st2 = st >> 4;
int ct2 = ct >> 4;
g.drawLine(x0 + ct2, y0 + st2, x0 + st, y0 - ct);
g.drawLine(x0 - ct2, y0 - st2, x0 + st, y0 - ct);
}
}
int paintCnt = 0;
public void paint(Graphics g) {
g.setColor(Color.black);
int m = minutes.getValue();
if (img != null)
{
g.drawImage(img, x0 - (d.width >> 1),
y0 - (d.height >> 1), this);
}
else {
g.setColor(Color.red);
int R = (x0 < y0 ? x0 : y0) * 8 / 10;
g.drawArc(x0 - R, y0 - R, R * 2, R * 2, 0, 360);
g.setColor(Color.black);
R -= 5;
g.drawArc(x0 - R, y0 - R, R * 2, R * 2, 90,-hours.getValue() * 30);
}
drawHand(g, hours.getValue() * 10 + m / 6, 0.8f, true);
drawHand(g, m * 2, 1.0f, false);
}
public void update(Graphics g) {
paint(g);
}
}
}
class CalButton extends JButton{
public boolean mouseUp;
private DateChooser.MiniCal myMiniCal;
private int myDirection;
private static ChangeCalRepeater scrollThread;
public CalButton(DateChooser.MiniCal minical,String text,int direction) {
super(text);
myMiniCal = minical;
myDirection = direction;
enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK |AWTEvent.MOUSE_EVENT_MASK);
}
protected void processMouseEvent(MouseEvent e){
if (e.getID()==e.MOUSE_PRESSED){
doSomething();
mouseUp = false;
if (scrollThread == null) {
// If there isn't a scrollThread, then create
// one and start it.
scrollThread = new ChangeCalRepeater(this);
scrollThread.start();
}
else{
scrollThread = null;
scrollThread = new ChangeCalRepeater(this);
scrollThread.start();
}
}
else if (e.getID()==e.MOUSE_RELEASED){
mouseUp = true;
}
}
public void doSomething(){
if (myDirection == 0){
myMiniCal.goBack();
}
else {
myMiniCal.goForward();
}
}
}
class ChangeCalRepeater extends Thread {
/**
* Time to pause before the first scroll repeat.
*/
static int beginPause = 650; // Reminder - make this a user definable property
/**
* Time to pause between each scroll repeat.
*/
static int repeatPause = 100; // Reminder - make this a user definable property
private boolean newChange;
private CalButton myCalButton;
ChangeCalRepeater(CalButton calbutton) {
super("ChangeCalRepeater thread");
newChange = true;
myCalButton = calbutton;
}
/**
* Called by Thread.start(). Starts the scroll repeater thread.
*/
public void run (){
//boolean shouldScroll; // local variable for thread safety
while (true){
if (newChange) {
try {
// Pause before repeating. This gives the user time to release
// the mouse button before continuous scrolling starts.
sleep(beginPause);
}
catch (java.lang.InterruptedException e) {}
newChange = false;
}
if(!(myCalButton.mouseUp)){
myCalButton.doSomething();
}
else{
stop();
}
try {
sleep(repeatPause);
}
catch (InterruptedException e) {}
}
}
}
class SyncTypeComboBoxEditor implements ComboBoxEditor{
private JComboBox myJComboBox;
private Spinner editor;
public SyncTypeComboBoxEditor(JComboBox jcombobox){
myJComboBox = jcombobox;
}
public Component getEditorComponent(){
return editor;
}
public void setEditorComponent(Spinner newEditor){
editor = newEditor;
}
public void setValue(Object anObject){
try {
//editor.setValue(myJComboBox.getCurrentValueIndex()+editor.getMinimum());
}
catch(IllegalComponentStateException e) {}
}
public Object getValue(){
return editor.getText();
}
public void selectAll(){}
public void addActionListener(ActionListener l){}
public void removeActionListener(ActionListener l){}
}
class SpinnerLinker implements KeyListener{
private Spinner sp1, sp2, sp3;
public SpinnerLinker(Spinner spinner1, Spinner spinner2, Spinner spinner3){
sp1 = spinner1;
sp2 = spinner2;
sp3 = spinner3;
}
public void keyTyped(KeyEvent e){}
public void keyPressed(KeyEvent e){
if (e.isActionKey())
switch(e.getKeyCode()) {
case e.VK_LEFT:
if(sp1.hasFocus()){
if (sp3 != null){sp3.requestFocus();}
else {sp2.requestFocus();}
}
else if(sp2.hasFocus()){
if (sp1 != null) {sp1.requestFocus();}
else {sp3.requestFocus();}
}
else{
if(sp2 != null){sp2.requestFocus();}
else{sp1.requestFocus();}
}
break;
case e.VK_RIGHT:
if(sp1.hasFocus()){
if(sp2!=null){sp2.requestFocus();}
else {sp3.requestFocus();}
}
else if(sp2.hasFocus()) {
if (sp3 != null) {sp3.requestFocus();}
else {sp1.requestFocus();}
}
else {
if (sp1 != null){sp1.requestFocus();}
else{sp2.requestFocus();}
}
break;
}
}
public void keyReleased(KeyEvent e){}
}
class FilledBorderedPane extends JPanel{
private Color fillColor;
public FilledBorderedPane(){
super();
setLayout(new FlowLayout(FlowLayout.CENTER,0,0));
}
public void paint(Graphics g) {
if(fillColor != null){
g.setColor(fillColor);
g.fillRect(0,0,getWidth(),getHeight());
}
super.paint(g);
}
public void setFillColor(Color newColor) {fillColor = newColor;}
}